using System;
using System.Collections.Generic;
using System.Linq;
using Headsight.BPMN2WF.BPMN.Schemas;
using Headsight.BPMN2WF.Testing.TestObjCtors;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;

namespace Headsight.BPMN2WF.Testing.TestObjCtorsTest
{
    [TestClass]
    public class tProcessCtorTest
    {
        private tProcessCtor _sut;
        private tFlowElement _tFlowElem;
        private tSequenceFlow _tSeqElem;

        [TestInitialize]
        public void SetUp()
        {
            _tFlowElem = new tFlowNodeCtor().Create();
            _tSeqElem = new tSequenceFlowCtor().Create();

            _sut = new tProcessCtor();
        }

        [TestMethod]
        public void Create()
        {
            var actual = _sut.Create();

            Assert.IsNotNull(actual);
            Assert.IsInstanceOfType(actual, typeof(tProcess));

            Assert.IsNotNull(actual.Items);
            Assert.AreEqual(0, actual.Items.Length);

            Assert.AreEqual("tProcessId", actual.id);
            Assert.AreEqual("tProcessName", actual.name);
        }

        [TestMethod]
        public void WithId_is_chaining()
        {
            Assert.AreSame(_sut, _sut.WithId("**id**"));
        }

        [TestMethod]
        public void WithId_Create()
        {
            var actual = _sut.WithId("**id**").Create();

            Assert.AreEqual("**id**", actual.id);
        }

        [TestMethod]
        public void WithName_is_chaining()
        {
            Assert.AreSame(_sut, _sut.WithName("**name**"));
        }

        [TestMethod]
        public void WithName_Create()
        {
            var actual = _sut.WithName("**name**").Create();

            Assert.AreEqual("**name**", actual.name);
        }

        [TestMethod]
        public void With_tFlowElement_is_chaining()
        {
            Assert.AreSame(_sut, _sut.With(_tFlowElem, _tSeqElem));
        }

        [TestMethod]
        public void With_tFlowElement()
        {
            var actual = _sut.With(_tFlowElem).With(_tSeqElem).With(_tFlowElem, _tSeqElem).Create();

            Assert.AreEqual(4, actual.Items.Length);
            Assert.AreSame(_tFlowElem, actual.Items[0]);
            Assert.AreSame(_tSeqElem, actual.Items[1]);
            Assert.AreSame(_tFlowElem, actual.Items[2]);
            Assert.AreSame(_tSeqElem, actual.Items[3]);
        }

        [TestMethod]
        public void With_tFlowElementTestObjCtor_is_chaining()
        {
            Assert.AreSame(_sut, _sut.With(MockRepository.GenerateStub<ITestObjCtor<tUserTask>>()));
        }

        [TestMethod]
        public void With_tFlowElementTestObjCtor_Create()
        {
            var tObjCtor1 = MockRepository.GenerateStub<ITestObjCtor<tUserTask>>();
            var tObjCtor2 = MockRepository.GenerateStub<ITestObjCtor<tExclusiveGateway>>();

            var tObjCtor1Result = new tUserTask();
            var tObjCtor2Result = new tExclusiveGateway();

            tObjCtor1.Expect(x => x.Create()).Return(tObjCtor1Result);
            tObjCtor2.Expect(x => x.Create()).Return(tObjCtor2Result);

            var actual = _sut.With(tObjCtor1).With(tObjCtor2).Create();

            Assert.AreEqual(2, actual.Items.Length);
            Assert.AreSame(tObjCtor1Result, actual.Items[0]);
            Assert.AreSame(tObjCtor2Result, actual.Items[1]);
        }

        [TestMethod]
        public void WithStartEvent_is_chaining()
        {
            Assert.AreSame(_sut, _sut.WithStartEvent("startEvent"));
        }

        [TestMethod]
        public void WithStartEvent_create()
        {
            var actual = _sut.WithStartEvent("startEvent").Create().Items[0] as tStartEvent;

            Assert.IsNotNull(actual);
            Assert.AreEqual("startEvent", actual.id);
            Assert.AreEqual("startEvent", actual.name);
        }

        [TestMethod]
        public void Flow_is_chaining()
        {
            Assert.AreSame(_sut, _sut.Flow("from", "to"));
        }

        [TestMethod]
        public void Flow_id1_to_id2_Create()
        {
            var actual = _sut.Flow("id1", "id2").Create().Items;

            Assert.AreEqual(3, actual.Length);

            var tFlowNodes = actual.Where(x => typeof(tFlowNode).IsAssignableFrom(x.GetType()));
            var tSeqFlows = actual.Where(x => typeof(tSequenceFlow).IsAssignableFrom(x.GetType())).Cast<tSequenceFlow>();

            Assert.AreEqual(2, tFlowNodes.Count());
            Assert.AreEqual(1, tSeqFlows.Count());

            Assert.IsNotNull(tFlowNodes.FirstOrDefault(x => x.id == "id1"));
            Assert.IsNotNull(tFlowNodes.FirstOrDefault(x => x.id == "id2"));
            Assert.IsNotNull(tSeqFlows.FirstOrDefault(x => x.id == "id1_id2" && x.sourceRef == "id1" && x.targetRef == "id2"));
        }

        [TestMethod]
        public void Flow_id1_to_id2_and_id3__id2_to_id1Create()
        {
            var actual = _sut.Flow("id1", "id2").Flow("id1", "id3").Flow("id2", "id1").Create().Items;

            Assert.AreEqual(6, actual.Length);

            var tFlowNodes = actual.Where(x => typeof(tFlowNode).IsAssignableFrom(x.GetType()));
            var tSeqFlows = actual.Where(x => typeof(tSequenceFlow).IsAssignableFrom(x.GetType())).Cast<tSequenceFlow>();

            Assert.AreEqual(3, tFlowNodes.Count());
            Assert.AreEqual(3, tSeqFlows.Count());

            Assert.IsNotNull(tFlowNodes.FirstOrDefault(x => x.id == "id1"));
            Assert.IsNotNull(tFlowNodes.FirstOrDefault(x => x.id == "id2"));
            Assert.IsNotNull(tFlowNodes.FirstOrDefault(x => x.id == "id3"));
            Assert.IsNotNull(tSeqFlows.FirstOrDefault(x => x.id == "id1_id2" && x.sourceRef == "id1" && x.targetRef == "id2"));
            Assert.IsNotNull(tSeqFlows.FirstOrDefault(x => x.id == "id1_id3" && x.sourceRef == "id1" && x.targetRef == "id3"));
            Assert.IsNotNull(tSeqFlows.FirstOrDefault(x => x.id == "id2_id1" && x.sourceRef == "id2" && x.targetRef == "id1"));
        }

        [TestMethod]
        public void Branch_is_chaining()
        {
            Assert.AreSame(_sut, _sut.Branch("id1", "id2"));
        }

        [TestMethod]
        public void Branch_inco_2_branches()
        {
            var actual = _sut
                .Branch("gateway_id", "branch_1_id")
                .Branch("gateway_id", "branch_2_id").Create().Items;

            Assert.AreEqual(3 + 2, actual.Length);
            Verify("gateway_id", typeof(tExclusiveGateway), actual);
            Verify("branch_1_id", typeof(tUserTask), actual);
            Verify("branch_2_id", typeof(tUserTask), actual);
            VerifyGatewayFlow("gateway_id", "branch_1_id", actual);
            VerifyGatewayFlow("gateway_id", "branch_2_id", actual);
        }

        [TestMethod]
        public void Branch_inco_2_branches_variation()
        {
            var actual = new tProcessCtor()
                .Flow("start", "gateway_id")
                .Branch("gateway_id", "branch_1_id")
                .Branch("gateway_id", "branch_2_id").Create().Items;

            Assert.AreEqual(4 + 3, actual.Length);
            Verify("start", typeof(tUserTask), actual);
            VerifyFlow("start", "gateway_id", actual);
            Verify("gateway_id", typeof(tExclusiveGateway), actual);
            Verify("branch_1_id", typeof(tUserTask), actual);
            Verify("branch_2_id", typeof(tUserTask), actual);
            VerifyGatewayFlow("gateway_id", "branch_1_id", actual);
            VerifyGatewayFlow("gateway_id", "branch_2_id", actual);
        }

        [TestMethod]
        public void WithStartEvent_Flow_Create()
        {
            var actual = _sut.WithStartEvent("startEvent").Flow("startEvent", "a").Create();

            var items = actual.Items;

            Assert.AreEqual(3, items.Length);
            Assert.IsInstanceOfType(items[0], typeof(tStartEvent));
            Assert.IsInstanceOfType(items[1], typeof(tUserTask));
        }

        [TestMethod]
        public void WithStartEvent_Branch_Create()
        {
            var actual = _sut.WithStartEvent("startEvent")
                .Flow("startEvent", "g")
                .Branch("g", "b1").Branch("g", "b2")
                .Create();

            var items = actual.Items;

            Assert.AreEqual(7, items.Length);
            Verify("startEvent", typeof(tStartEvent), items);
            Verify("g", typeof(tExclusiveGateway), items);
            Verify("b1", typeof(tUserTask), items);
            Verify("b2", typeof(tUserTask), items);

            VerifyFlow("startEvent", "g", items);
            VerifyGatewayFlow("g", "b1", items);
            VerifyGatewayFlow("g", "b2", items);
        }

        private static void VerifyFlow(String id1, String id2, IEnumerable<tFlowElement> actual)
        {
            var expectedSeqFlowId = id1 + "_" + id2;

            var seqFlow = (tSequenceFlow)actual.First(x => x.id == expectedSeqFlowId);

            Assert.AreEqual(id1, seqFlow.sourceRef);
            Assert.AreEqual(id2, seqFlow.targetRef);
        }

        private static void VerifyGatewayFlow(String id1, String id2, IEnumerable<tFlowElement> actual)
        {
            var expectedSeqFlowId = id1 + "_" + id2;

            var seqFlow = (tSequenceFlow)actual.First(x => x.id == expectedSeqFlowId);

            Assert.AreEqual(id1, seqFlow.sourceRef);
            Assert.AreEqual(id2, seqFlow.targetRef);
            Assert.IsNotNull(seqFlow.conditionExpression);
            Assert.AreEqual(1, seqFlow.conditionExpression.Text.Length);
            Assert.AreEqual("True = False", seqFlow.conditionExpression.Text[0]);
        }

        private static void Verify(String id, Type expectedElementType, IEnumerable<tFlowElement> actual)
        {
            var tFlowElement = actual.First(x => x.id == id);

            Assert.IsInstanceOfType(tFlowElement, expectedElementType);
        }
    }
}